/* caStore.c */

/*
 * Licensed to the ConArtist Development Team (CADT).
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 * The CADT licenses this file to you under the Apache License, 
 * Version 2.0 (the "License"); you may not use this file except in
 * compliance with the License.
 * You may obtain a copy of the License at
 * 
 *     http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <string.h>

#include "conartist.h"
#include "caStore.h"
#include "caLog.h"

#include "svn_client.h"
#include "svn_cmdline.h"
#include "svn_pools.h"
#include "svn_config.h"
#include "svn_fs.h"

struct svnStore {
    apr_pool_t *pool;           /* pointer to store's memory pool */
    char *url;                  /* URL of repository we are using */
    char *local_path;           /* path where data files are stored */
    struct caStore *store;      /**< Back pointer to store */

    /* SVN variables */
    apr_pool_t *svn_pool;
    svn_opt_revision_t revision;
    svn_client_ctx_t *ctx;

    svn_ra_session_t *session;

    svn_revnum_t local_revnum;
    svn_revnum_t remote_revnum;

    svn_ra_callbacks2_t callbacks;

    apr_array_header_t *providers;
};

static apr_pool_t *svnRemotePool = NULL;

/*
typedef struct svn_ra_callbacks2_t
{
    svn_error_t *(*open_tmp_file)(apr_file_t **fp,
                                  void *callback_baton,
                                  apr_pool_t *pool);

    svn_auth_baton_t *auth_baton;
    svn_ra_get_wc_prop_func_t get_wc_prop;
    svn_ra_set_wc_prop_func_t set_wc_prop;
    svn_ra_push_wc_prop_func_t push_wc_prop;
    svn_ra_invalidate_wc_props_func_t invalidate_wc_props;
    svn_ra_progress_notify_func_t progress_func;
    void *progress_baton;
} svn_ra_callbacks2_t;
*/

/*
svn_error_t *
 svn_client_checkout2(svn_revnum_t *result_rev,
                      const char *URL,
                      const char *path,
                      const svn_opt_revision_t *peg_revision,
                      const svn_opt_revision_t *revision,
                      svn_boolean_t recurse,
                      svn_boolean_t ignore_externals,
                      svn_client_ctx_t *ctx,
                      apr_pool_t *pool);
*/

static svn_error_t *svnCheckout(struct svnStore *ss)
{
    svn_opt_revision_t peg_rev;
    svn_opt_revision_t rev;
    svn_error_t *err;

    caLog(CA_LOG_INFO, "Trying to checkout %s into %s",
          apr_table_get(ss->store->config, "Path"),
          ss->local_path);

    peg_rev.kind = svn_opt_revision_head;
    rev.kind = svn_opt_revision_head;

    err = svn_client_checkout2(&ss->local_revnum,
                                apr_table_get(ss->store->config, "Path"),
                                ss->local_path,
                                &peg_rev, &rev,
                                TRUE, FALSE,
                                ss->ctx,
                                ss->svn_pool);
    if (err != SVN_NO_ERROR) {
        char buff[256];
        svn_strerror(err->apr_err, buff, 256);
        caLog(CA_LOG_ERROR, "Checkout of '%s' into '%s' failed\n"
              "SVN Error: %s", apr_table_get(ss->store->config, "Path"),
              ss->local_path, buff);
        return err;
    } else
        caLog(CA_LOG_INFO, "Checkout completed");

    return SVN_NO_ERROR;    
}

static svn_error_t *svnOpenTempFile(apr_file_t **fp, void *baton, 
                                    apr_pool_t *pool)
{
    return SVN_NO_ERROR;
}

svn_ra_callbacks2_t svnCallbacks = {
    svnOpenTempFile,
    NULL,
    NULL,
    NULL,
    NULL,
    NULL,
    NULL,
    NULL,
};

static svn_error_t *user_password_callback (svn_auth_cred_simple_t **cred,
                                            void *baton,
                                            const char *realm,
                                            const char *username,
                                            svn_boolean_t may_save,
                                            apr_pool_t *pool)
{
    struct svnStore *ss = (struct svnStore *)baton;
    svn_auth_cred_simple_t *ret = apr_pcalloc (pool, sizeof (*ret));
    ret->username = apr_pstrdup (pool, 
                                 apr_table_get(ss->store->config, "Username"));
    ret->password = apr_pstrdup (pool, 
                                 apr_table_get(ss->store->config, "Password"));
    ret->may_save = 0;

    *cred = ret;
    return SVN_NO_ERROR;
}

static svn_error_t * username_callback(svn_auth_cred_username_t **cred,
                                       void *baton,
                                       const char *realm,
                                       svn_boolean_t may_save,
                                       apr_pool_t *pool)
{
    struct svnStore *ss = (struct svnStore *)baton;
    svn_auth_cred_username_t *ret = apr_pcalloc (pool, sizeof (*ret));
    ret->username = apr_pstrdup (pool, 
                                 apr_table_get(ss->store->config, "Username"));

    *cred = ret;
    return SVN_NO_ERROR;
}

static svn_error_t *openSession(struct svnStore *ss)
{
    svn_error_t *err;

    ss->callbacks = svnCallbacks;
    ss->callbacks.auth_baton = ss->ctx->auth_baton;

    if (ss->session)
        return 0;

    err = svn_ra_open2(&ss->session,
                        apr_table_get(ss->store->config, "Path"),
                        &ss->callbacks, ss,
                        ss->ctx->config, ss->svn_pool);
    return err;
}

static svn_error_t *getLocalRevision(struct svnStore *ss)
{
    svn_wc_revision_status_t *res;
    svn_error_t *err;

    err = svn_wc_revision_status(&res, ss->local_path, NULL,
                                 FALSE, NULL, NULL, ss->svn_pool);
    if (err != SVN_NO_ERROR)
        return err;
    ss->local_revnum = res->max_rev;
    return SVN_NO_ERROR;
}

static int svnCheckLocal(struct svnStore *ss)
{
    svn_error_t *err;
    int wc_format;

    err = svn_wc_check_wc(ss->local_path, &wc_format, ss->svn_pool);
    if (err != SVN_NO_ERROR || !wc_format)
        return 1;

    return 0;
}

static int svnLocalUpdate(struct svnStore *ss)
{
    svn_error_t *err;
    apr_array_header_t *targets;
    svn_opt_revision_t rev;

    rev.kind = svn_opt_revision_head;

    targets = apr_array_make(ss->pool, 1, sizeof(char *));
    APR_ARRAY_PUSH(targets, const char *) = ss->local_path;

    err = svn_client_update2(NULL, targets, &rev,
                             TRUE, TRUE, ss->ctx, ss->pool);
    return err->apr_err;
}

static svn_error_t *getLatestRemoteRevision(struct svnStore *ss)
{
    svn_error_t *err;

    err = openSession(ss);
    if (err == SVN_NO_ERROR)
        err = svn_ra_get_latest_revnum(ss->session, &ss->remote_revnum,
                                       ss->svn_pool);
    printf("Latest revision = %ld\n", ss->remote_revnum);
    return err;
}

apr_status_t openSVNStore(struct caStore *st)
{
    struct svnStore *ss = NULL;
    svn_error_t *err;
    svn_auth_provider_object_t *provider = NULL;
    apr_status_t rv;

    if (!st)
        return APR_EINVAL;

    ss = apr_pcalloc(st->st_pool, sizeof(*ss));
    if (!ss)
        goto clean_exit;

    ss->pool = st->st_pool;
    ss->store = st;

    rv = apr_filepath_merge(&ss->local_path,
                            apr_table_get(st->config, "LocalPath"),
                            "wc",
                            APR_FILEPATH_TRUENAME, ss->pool);
    if (rv != APR_SUCCESS)
        goto clean_exit;

    ss->svn_pool = svn_pool_create (st->st_pool);
    err = svn_fs_initialize (ss->svn_pool);
    if (err)
        goto clean_exit;

    err = svn_client_create_context (&ss->ctx, ss->svn_pool);
    if (err)
        goto clean_exit;

    err = svn_config_ensure (NULL, ss->svn_pool);
    if (err)
        goto clean_exit;

    err = svn_config_get_config (&(ss->ctx->config), NULL, ss->svn_pool);
    if (err)
        goto clean_exit;

    ss->providers = apr_array_make (ss->svn_pool, 4, 
                                    sizeof(svn_auth_provider_object_t *));

    svn_auth_get_simple_prompt_provider(&provider,
                                        user_password_callback,
                                        NULL, 1, ss->svn_pool);
    APR_ARRAY_PUSH (ss->providers, svn_auth_provider_object_t *) = provider;

    svn_auth_get_username_prompt_provider(&provider,
                                          username_callback,
                                          NULL, 1, ss->svn_pool);
    APR_ARRAY_PUSH (ss->providers, svn_auth_provider_object_t *) = provider;

    /* Register the auth-providers into the context's auth_baton. */
    svn_auth_open (&ss->ctx->auth_baton, ss->providers, ss->svn_pool);      

    
    if (svnCheckLocal(ss)) {
        if (svnCheckout(ss) != SVN_NO_ERROR) {
            caLog(CA_LOG_ERROR, "Unable to checkout remote SVN repository "
                  " to '%s'", ss->local_path);
            goto clean_exit;
        }
    } else {
        getLocalRevision(ss);
        getLatestRemoteRevision(ss);
        if (ss->remote_revnum != ss->local_revnum)
            svnLocalUpdate(ss);
    }
    st->privateData = ss;

    return APR_SUCCESS;

clean_exit:
    if (ss->svn_pool)
        apr_pool_destroy(ss->svn_pool);
    apr_pool_destroy(st->st_pool);
    return APR_EGENERAL;
}

void closeSVNStore(struct caStore *store)
{
    /* TODO - shutdown notification thread when added */
}

apr_status_t svnGetPath(char **rStr, const char *path, apr_pool_t *p,
                        void *data)
{
    struct svnStore *ss = (struct svnStore *)data;

    if (path && path[0] == '/')
        return apr_filepath_merge(rStr, ss->local_path, &path[1], 
                                  APR_FILEPATH_TRUENAME, p);
    else
        return apr_filepath_merge(rStr, ss->local_path, path, 
                                  APR_FILEPATH_TRUENAME, p);

}

static apr_status_t svnremoteInit(apr_pool_t *pool)
{
    apr_status_t rv = apr_pool_create(&svnRemotePool, pool);
    if (rv != APR_SUCCESS)
        return rv;
    return APR_SUCCESS;
}

static apr_status_t svnremoteFini(void)
{
    svnRemotePool = NULL;
    return APR_SUCCESS;
}

struct caStoreModule ConArtistModule = {
    {
        0,                           /* verMajor */
        1,                           /* verMinor */
        "Subversion Remote Store",   /* name */
        CAM_STORE,                   /* type */
        svnremoteInit,               /* init */
        svnremoteFini,               /* fini */
    },
    "svn_remote",
    &openSVNStore,                   /* openStore */
    &closeSVNStore,                  /* closeStore */
    &svnGetPath,                     /* getPath */
    NULL,                            /* openDir */
    NULL,                            /* readDir */
    NULL,                            /* closeDir */
    NULL,                            /* openFile */
    NULL,                            /* closeFile */
    NULL,                            /* readFile */
    NULL,                            /* writeFile */
};
